﻿using System;
using System.Linq;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;

namespace ScriptXCompiler
{
    public static class Extensions
    {
        #region modifier
        public static bool IsValidMemberKind(this MemberDeclarationSyntax syntax)
        {
            switch (syntax.Kind)
            {
                case SyntaxKind.FieldDeclaration:
                case SyntaxKind.ConstructorDeclaration:
                case SyntaxKind.PropertyDeclaration:
                case SyntaxKind.MethodDeclaration:
                    return true;

                default:
                    return false;
            }
        }

        /// <summary>
        /// Only valid for field/constructor/property/method
        /// </summary>
        /// <param name="syntax"></param>
        /// <returns></returns>
        public static bool IsStaticMember(this MemberDeclarationSyntax syntax)
        {
            SyntaxTokenList modifiers = new SyntaxTokenList();
            switch (syntax.Kind)
            {
                case SyntaxKind.FieldDeclaration:
                    var field = syntax as FieldDeclarationSyntax;
                    modifiers = field.Modifiers;
                    break;

                case SyntaxKind.PropertyDeclaration:
                    var property = syntax as PropertyDeclarationSyntax;
                    modifiers = property.Modifiers;
                    break;

                case SyntaxKind.MethodDeclaration:
                    var method = syntax as MethodDeclarationSyntax;
                    modifiers = method.Modifiers;
                    break;

                case SyntaxKind.ConstructorDeclaration:
                    var ctor = syntax as ConstructorDeclarationSyntax;
                    modifiers = ctor.Modifiers;
                    break;
            }
            foreach (var modifier in modifiers)
            {
                if (modifier.Kind == SyntaxKind.StaticKeyword)
                    return true;
            }
            return false;
        }

        public static bool IsStaticType(this ClassDeclarationSyntax syntax)
        {
            foreach (var modifier in syntax.Modifiers)
            {
                if (modifier.Kind == SyntaxKind.StaticKeyword)
                    return true;
            }
            return false;
        }

        public static bool IsPartial(this ClassDeclarationSyntax syntax)
        {
            foreach (var modifier in syntax.Modifiers)
            {
                if (modifier.Kind == SyntaxKind.PartialKeyword)
                    return true;
            }
            return false;
        }

        public static bool IsPartial(this MethodDeclarationSyntax syntax)
        {
            foreach (var modifier in syntax.Modifiers)
            {
                if (modifier.Kind == SyntaxKind.PartialKeyword)
                    return true;
            }
            return false;
        }

        public static bool IsExtern(this MethodDeclarationSyntax syntax)
        {
            foreach (var modifier in syntax.Modifiers)
            {
                if (modifier.Kind == SyntaxKind.ExternKeyword)
                    return true;
            }
            return false;
        }

        public static bool IsUnSafe(this MethodDeclarationSyntax syntax)
        {
            foreach (var modifier in syntax.Modifiers)
            {
                if (modifier.Kind == SyntaxKind.UnsafeKeyword)
                    return true;
            }
            return false;
        }

        public static bool IsUnSafe(this ClassDeclarationSyntax syntax)
        {
            foreach (var modifier in syntax.Modifiers)
            {
                if (modifier.Kind == SyntaxKind.UnsafeKeyword)
                    return true;
            }
            return false;
        }

        public static bool IsExtensionParameter(this ParameterSyntax syntax)
        {
            foreach (var modifier in syntax.Modifiers)
            {
                if (modifier.Kind == SyntaxKind.ThisKeyword)
                    return true;
            }
            return false;
        }

        public static bool IsEnumerable(this TypeInfo info)
        {
            //Extend:More type, IEnumerable<T>...
            if (info.Type.TypeKind == TypeKind.ArrayType || info.Type.SpecialType == SpecialType.System_Array || info.Type.SpecialType == SpecialType.System_Collections_IEnumerable)
                return true;
            foreach (var inf in info.Type.AllInterfaces)
            {
                if (inf.IsSameType(System_Collections_Namespace, IEnumerable_Interface))
                    return true;
            }
            return false;
        }
        #endregion

        private static bool IsSameType(this TypeSymbol type, string ns, string name)
        {
            return type.ContainingNamespace.ToString() == ns && type.Name == name;
        }

        #region attribute
        private const string System_Namespace = "System";
        private const string System_Collections_Namespace = "System.Collections";
        private const string GlobalVariable_Attribute = "GlobalVariableAttribute";
        private const string JsNative_Attribute = "JsNativeAttribute";
        private const string EvalAtCompile_Attribute = "EvalAtCompileAttribute";
        private const string NonScript_Attribute = "NonScriptAttribute";
        private const string NoCompile_Attribute = "NoCompileAttribute";
        private const string EnumValue_Attribute = "EnumValueAttribute";
        private const string ImportMember_Attribute = "ImportMemberAttribute";
        private const string ImportClass_Attribute = "ImportClassAttribute";
        private const string ScriptDefaultValue_Attribute = "ScriptDefaultValueAttribute";
        private const string IEnumerable_Interface = "IEnumerable";
        private const string Json_Name_Quote_Attribute = "JsonNameQuoteAttribute";

        public static bool IsGlobalVariable(this MemberDeclarationSyntax member, SemanticModel model)
        {
            if (member.Kind != SyntaxKind.FieldDeclaration)
                return false;
            var field = member as FieldDeclarationSyntax;
            var attrDeclares = field.AttributeLists;
            foreach (var attrDeclare in attrDeclares)
            {
                foreach (var attr in attrDeclare.Attributes)
                {
                    var type = model.GetTypeInfo(attr);
                    if (type.Type.IsSameType(System_Namespace, GlobalVariable_Attribute))
                        return true;
                }
            }

            return false;
        }

        public static bool IsGlobalVariable(this Symbol symbol)
        {
            var attrs = symbol.GetAttributes();
            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.IsSameType(System_Namespace, GlobalVariable_Attribute))
                    return true;
            }
            return false;
        }

        public static bool? GetJsonQuote(this Symbol symbol)
        {
            var attrs = symbol.GetAttributes();

            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.IsSameType(System_Namespace, Json_Name_Quote_Attribute))
                {
                    var add = attr.ConstructorArguments.ElementAt(0).Value;
                    return (bool)add;
                }
            }

            return null;
        }

        public static bool IsNativeMethod(this Symbol symbol)
        {
            var attrs = symbol.GetAttributes();
            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.IsSameType(System_Namespace, JsNative_Attribute))
                    return true;
            }
            return false;
        }

        public static bool IsScriptSymbol(this Symbol symbol)
        {
            var attrs = symbol.GetAttributes();
            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.IsSameType(System_Namespace, NonScript_Attribute))
                    return false;
            }
            return true;
        }

        public static bool IsEvalCandidate(this Symbol symbol)
        {
            var attrs = symbol.GetAttributes();
            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.IsSameType(System_Namespace, EvalAtCompile_Attribute))
                    return true;
            }
            return false;
        }

        public static bool IsEvalCandidate(this MemberDeclarationSyntax member, SemanticModel model)
        {
            if (member.Kind != SyntaxKind.FieldDeclaration)
                return false;
            var field = member as FieldDeclarationSyntax;
            var attrDeclares = field.AttributeLists;
            foreach (var attrDeclare in attrDeclares)
            {
                foreach (var attr in attrDeclare.Attributes)
                {
                    var type = model.GetTypeInfo(attr);
                    if (type.Type.IsSameType(System_Namespace, EvalAtCompile_Attribute))
                        return true;
                }
            }

            return false;
        }

        public static bool IsNoCompile(this Symbol symbol)
        {
            foreach (var attr in symbol.GetAttributes())
            {
                if (attr.AttributeClass.IsSameType(System_Namespace, NoCompile_Attribute))
                    return true;
            }
            return false;
        }

        public static bool IsNoCompile(this BaseFieldDeclarationSyntax syntax, SemanticModel model)
        {
            foreach (var attrListSyntax in syntax.AttributeLists)
            {
                foreach (var attr in attrListSyntax.Attributes)
                {
                    var type = model.GetTypeInfo(attr);
                    if (type.Type.IsSameType(System_Namespace, NoCompile_Attribute))
                        return true;
                }
            }
            return false;
        }

        public static bool IsNoCompile(this BaseMethodDeclarationSyntax syntax, SemanticModel model)
        {
            foreach (var attrListSyntax in syntax.AttributeLists)
            {
                foreach (var attr in attrListSyntax.Attributes)
                {
                    var type = model.GetTypeInfo(attr);
                    if (type.Type.IsSameType(System_Namespace, NoCompile_Attribute))
                        return true;
                }
            }
            return false;
        }

        public static string GetMemberSymbolName(this FieldDeclarationSyntax syntax, SemanticModel model)
        {
            var attrDeclares = syntax.AttributeLists;
            foreach (var attrDeclare in attrDeclares)
            {
                foreach (var attr in attrDeclare.Attributes)
                {
                    var type = model.GetTypeInfo(attr);
                    if (type.Type.IsSameType(System_Namespace, ImportMember_Attribute))
                    {
                        var val = model.GetConstantValue(attr.ArgumentList.Arguments[0].Expression);
                        return (string)val.Value;
                    }
                }
            }
            var v = syntax.Declaration.Variables[0];//Note:Field declare always declare 1 var
            return NamingConvention.LowerCase1stChar(v.Identifier.ValueText);
        }

        public static string GetMemberSymbolName(this Symbol symbol)
        {
            var attrs = symbol.GetAttributes();
            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.IsSameType(System_Namespace, ImportMember_Attribute))
                {
                    var val = attr.ConstructorArguments.ElementAt(0).Value;
                    return (string)val;
                }
            }

            return NamingConvention.LowerCase1stChar(symbol.Name);
        }

        public static string GetTypeSymbolName(this Symbol symbol)
        {
            var name = string.Empty;
            var attrs = symbol.GetAttributes();
            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.IsSameType(System_Namespace, ImportClass_Attribute))
                {
                    var ns = attr.ConstructorArguments.ElementAt(0).Value;
                    var cs = attr.ConstructorArguments.ElementAt(1).Value;

                    var fqn = string.Format("{0}.{1}", ns, cs);
                    name = fqn.TrimStart('.').TrimEnd('.');

                    return name;
                }
            }

            return symbol.ContainingSymbol + "." + symbol.Name;
        }

        public static bool IsRightMostMemberAccess(this MemberAccessExpressionSyntax node)
        {
            var parent = node.Parent;

            while (parent.Kind == SyntaxKind.ElementAccessExpression)
                parent = parent.Parent;
            if (parent.Kind == SyntaxKind.MemberAccessExpression)
                return false;
            else
                return true;
        }

        public static bool ReturnValue(this TypeInfo info)
        {
            if (info.ConvertedType.TypeKind == TypeKind.Delegate)
            {
                if (info.ConvertedType.Name == "Func")
                    return true;
                //Note:Action is false;
            }
            return false;
        }

        public static bool GetEnumValue(this Symbol symbol, out string val)
        {
            foreach (var attr in symbol.GetAttributes())
            {
                if (attr.AttributeClass.IsSameType(System_Namespace, EnumValue_Attribute))
                {
                    if (attr.ConstructorArguments.ElementAt(0).Value == null)
                        val = null;
                    else
                        val = string.Format("\"{0}\"", attr.ConstructorArguments.ElementAt(0).Value);
                    return true;
                }
            }

            val = null;
            return false;
        }

        public static string GetDefaultValueOfType(this TypeSyntax type, SemanticModel model)
        {
            var info = model.GetTypeInfo(type);
            var cType = info.ConvertedType;

            if (info.IsEnumerable())
            {
                return "[]";
            }

            var attrs = cType.GetAttributes();
            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.IsSameType(System_Namespace, ScriptDefaultValue_Attribute))
                {
                    var val = attr.NamedArguments.ElementAt(0).Value;
                    return (string)val.Value;
                }
            }

            return ("null");
        }
        #endregion
    }
}
